const fs = require('fs')
const path = require('path')
const { format } = require('date-fns')
const express = require('express')
const chokidar = require('chokidar')
const http = require('http')
const WebSocket = require('ws')

const { NodeIO } = require('@gltf-transform/core')
const { ALL_EXTENSIONS } = require('@gltf-transform/extensions')
const { mat4, vec3, quat } = require('gl-matrix')
const draco3d = require('draco3d')

let MODELS_DIR = path.join(__dirname, 'models')
let SERVE_MODE = false
let PORT = 3000

const args = process.argv.slice(2)
for (let i = 0; i < args.length; i++) {
  if (args[i] === '-p' && args[i + 1]) {
    MODELS_DIR = path.resolve(args[i + 1])
  }
  if (args[i] === '-serve') {
    SERVE_MODE = true
  }
  if (args[i] === '-port' && args[i + 1]) {
    PORT = parseInt(args[i + 1])
  }
}

const timestamp = format(new Date(), 'yyyyMMdd_HHmmss')
const OUTPUT_FILE = path.join(__dirname, `output_${timestamp}.json`)
let result = {}
const warnings = []

async function getModelSize(filePath) {
  try {
    const encoderModule = await draco3d.createEncoderModule()
    const decoderModule = await draco3d.createDecoderModule()

    const io = new NodeIO()
      .registerExtensions(ALL_EXTENSIONS)
      .registerDependencies({
        'draco3d.decoder': decoderModule,
        'draco3d.encoder': encoderModule,
      })

    const document = await io.read(filePath)
    const root = document.getRoot()
    const scenes = root.listScenes()

    let globalMin = [Infinity, Infinity, Infinity]
    let globalMax = [-Infinity, -Infinity, -Infinity]

    for (const scene of scenes) {
      for (const node of scene.listChildren()) {
        await traverseNode(node, mat4.create())
      }
    }

    const size = [
      Number((globalMax[0] - globalMin[0]).toFixed(3)),
      Number((globalMax[1] - globalMin[1]).toFixed(3)),
      Number((globalMax[2] - globalMin[2]).toFixed(3)),
    ]
    return size

    // ⛏️ 自己构造局部矩阵，并递归传递
    async function traverseNode(node, parentMatrix) {
      const translation = node.getTranslation() || [0, 0, 0]
      const rotation = node.getRotation() || [0, 0, 0, 1]
      const scale = node.getScale() || [1, 1, 1]

      const localMatrix = mat4.fromRotationTranslationScale(
        mat4.create(),
        quat.fromValues(...rotation),
        vec3.fromValues(...translation),
        vec3.fromValues(...scale)
      )

      const worldMatrix = mat4.multiply(mat4.create(), parentMatrix, localMatrix)

      if (node.getMesh()) {
        const mesh = node.getMesh()
        for (const prim of mesh.listPrimitives()) {
          const position = prim.getAttribute('POSITION')
          if (!position) continue

          const array = position.getArray()
          const itemSize = position.getElementSize()
          const count = array.length / itemSize

          for (let i = 0; i < count; i++) {
            const vertex = vec3.fromValues(
              array[i * itemSize],
              array[i * itemSize + 1],
              array[i * itemSize + 2]
            )
            vec3.transformMat4(vertex, vertex, worldMatrix)

            for (let j = 0; j < 3; j++) {
              globalMin[j] = Math.min(globalMin[j], vertex[j])
              globalMax[j] = Math.max(globalMax[j], vertex[j])
            }
          }
        }
      }

      for (const child of node.listChildren()) {
        await traverseNode(child, worldMatrix)
      }
    }
  } catch (e) {
    console.warn(`❌ 获取模型尺寸失败: ${filePath}\n→ ${e.message}`)
    return { x: null, y: null, z: null }
  }
}
async function generateConfig() {
  result = {}
  warnings.length = 0

  const categories = fs.readdirSync(MODELS_DIR).filter((dir) => {
    if (dir === '.DS_Store') return false
    const fullPath = path.join(MODELS_DIR, dir)
    return fs.statSync(fullPath).isDirectory()
  })

  for (const category of categories) {
    const categoryPath = path.join(MODELS_DIR, category)
    const modelsPath = path.join(categoryPath, 'models')
    const previewsPath = path.join(categoryPath, 'previews')

    if (!fs.existsSync(modelsPath) || !fs.existsSync(previewsPath)) {
      console.warn(
        `⚠️  Skipping "${category}" due to missing models or previews folder.`
      )
      return
    }

    const modelFiles = fs
      .readdirSync(modelsPath)
      .filter(
        (f) =>
          f !== '.DS_Store' && fs.statSync(path.join(modelsPath, f)).isFile()
      )
    const previewFiles = fs
      .readdirSync(previewsPath)
      .filter(
        (f) =>
          f !== '.DS_Store' && fs.statSync(path.join(previewsPath, f)).isFile()
      )

    const previewMap = {}
    previewFiles.forEach((file) => {
      const name = path.parse(file).name
      previewMap[name] = file
    })

    result[category] = []

    for (const modelFile of modelFiles) {
      const modelName = path.parse(modelFile).name
      const previewFile = previewMap[modelName]

      if (!previewFile) {
        warnings.push(
          `⚠️  Missing preview image for model: ${category}/models/${modelFile}`
        )
      }

      const modelPathAbs = path.join(modelsPath, modelFile)
      const size = await getModelSize(modelPathAbs)

      result[category].push({
        name: modelName,
        category,
        modelPath: path.join(category, 'models', modelFile),
        previewPath: previewFile
          ? path.join(category, 'previews', previewFile)
          : null,
        size,
      })
    }
  }
}

const { makeMCPconfig } = require('./utils.js')

function startServer() {
  const app = express()
  const server = http.createServer(app)
  const wss = new WebSocket.Server({ server })

  app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', '*')
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
    next()
  })

  app.use('/static', express.static(MODELS_DIR))

  app.get('/config', (req, res) => {
    res.json({
      isGenerateModeServe: true,
      prePath: `http://localhost:${PORT}/static/`,
      data: result,
    })
  })
    app.get('/MCPconfig', (req, res) => {
    res.json({
      isGenerateModeServe: true,
      data: makeMCPconfig(result),
    })
  })

  app.get('/', (req, res) => {
    res.send(generateHTML(result))
  })

  server.listen(PORT, () => {
    console.log(`🚀 本地模型浏览服务已启动：http://localhost:${PORT}`)
    console.log(`📦 配置文件接口：http://localhost:${PORT}/config`)
    console.log(`📦 MCP配置文件：http://localhost:${PORT}/MCPconfig`)
  })

  chokidar.watch(MODELS_DIR, { ignoreInitial: true }).on('all', () => {
    console.log('📦 模型目录变动，重新生成配置...')
    generateConfig().then(() => {
      const payload = JSON.stringify({ type: 'update' })
      wss.clients.forEach((client) => {
        if (client.readyState === WebSocket.OPEN) {
          client.send(payload)
        }
      })
    })
  })
}

function generateHTML(data) {
  const categories = Object.keys(data)
  const defaultCategory = categories[0]

  return `<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>模型服务</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      background-color: #f5f5f5;
      margin: 0;
      padding: 2em;
    }
    h1 {
      text-align: center;
    }
    .info-bar {
      text-align: center;
      margin-bottom: 1em;
    }
    .categories {
      text-align: center;
      margin-bottom: 2em;
    }
    .categories button {
      margin: 0 5px;
      padding: 0.5em 1em;
      border: none;
      border-radius: 4px;
      background: #007bff;
      color: white;
      cursor: pointer;
    }
    .categories button.active {
      background: #0056b3;
    }
    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
      gap: 1.5em;
    }
    .card {
      background: white;
      padding: 1em;
      border-radius: 8px;
      box-shadow: 0 0 10px rgba(0,0,0,0.05);
    }
    .card img {
      max-width: 100%;
      max-height: 200px;
      object-fit: contain;
      margin-top: 0.5em;
      border-radius: 4px;
    }
  </style>
</head>
<body>
  <h1>🗂 模型浏览服务</h1>
  <div class="info-bar">
    📄 配置接口：<a href="/config" target="_blank">/config</a> · 共 ${
      categories.length
    } 个分类
  </div>

  <div class="categories" id="categoryBar">
    ${categories
      .map(
        (cat, i) =>
          `<button onclick="renderCategory('${cat}')" id="btn-${cat}" ${
            i === 0 ? 'class="active"' : ''
          }>${cat}</button>`
      )
      .join('')}
  </div>

  <div class="grid" id="modelGrid"></div>

  <script>
    const allData = ${JSON.stringify(data)};
    const categories = Object.keys(allData);

    function renderCategory(category) {
      // 更新按钮样式
      categories.forEach(cat => {
        document.getElementById('btn-' + cat).classList.remove('active');
      });
      document.getElementById('btn-' + category).classList.add('active');

      // 渲染模型卡片
      const grid = document.getElementById('modelGrid');
      grid.innerHTML = '';

      allData[category].forEach(item => {
        const card = document.createElement('div');
        card.className = 'card';
        card.innerHTML = \`
          <strong>模型名称：\${item.name}</strong><br/>
		  \${item.previewPath 
            ? '<img src="/static/' + item.previewPath + '" alt="预览图">' 
            : '<span style="color:red;">❌ 缺少预览图</span>'}
		  <br/>
          <small>模型路径：<br/></small><a href="\${'/static/'+item.modelPath}" target="_blank">\${'/static/'+item.modelPath}</a><br/>
		  <small>预览图路径：<br/></small><code> \${item.previewPath 
            ? '/static/'+item.previewPath
            : '❌ 缺少预览图'}
		  </code><br/>
      <small>模型尺寸：</small>
      <code>\${item.size && item.size[0] !== null ? \`\${item.size[0]} × \${item.size[1]} × \${item.size[2]}\` : '❌ 无尺寸数据'}</code>
        \`;
        grid.appendChild(card);
      });
    }

    // 初始化默认分类
    renderCategory("${defaultCategory}");
  </script>
</body>
</html>`
}

generateConfig().then(() => {
  if (SERVE_MODE) {
    startServer()
  } else {
    const jsonConfig = {
      isGenerateModeServe: true,
      prePath: `./models/`,
      data: result,
    }
    fs.writeFileSync(OUTPUT_FILE, JSON.stringify(jsonConfig, null, 2), 'utf-8')
    console.log(`✅ 已写入模型目录配置: ${OUTPUT_FILE}`)
    if (warnings.length) {
      console.warn('\n=== 缺失预览图 ===')
      warnings.forEach((w) => console.warn(w))
    } else {
      console.log('🎉 所有模型都有预览图。')
    }
  }
})
